#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# benyue.py - library web management system
# Copyright (C) 2011 zhangyan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# zhangyan <cdqzzy@gmail.com>

# globals settings {{{
my_version=" benyue.py,  zhangyan <cdqzzy@gmail.com>, $Format:%cd$"
my_local_version=''
my_location='/benyue.py'
my_database='/tmp/benyue.sqlite'
my_title='Title'
my_heading='Title'
my_bottom_line=''
my_logdir='/tmp/'
my_cgitb_display=1
my_contact='zhangyan <cdqzzy@gmail.com>'
my_admin_port=None
my_under_maintenence=0
try:
    from my_benyue_settings import *
except ImportError:
    pass
# }}}

# init database {{{
def init_database():
    # default passwd is '0000'
    sql_schema = '''
create table if not exists bookInstances
(
serial TEXT PRIMARY KEY NOT NULL,
bookid TEXT CONTRAINT fk_bkInst_exist REFERENCES books( bookid ) NOT NULL,
override_circulatable NUM
);

create table if not exists books
(
bookid TEXT NOT NULL PRIMARY KEY,
book_category TEXT REFERENCES bookCategories(book_category_text) NOT NULL,
book_num_in_category INT NOT NULL,
title TEXT NOT NULL,
author TEXT,
publisher TEXT,
price NUM,
isbn TEXT,
circulatable NUM NOT NULL DEFAULT 1
);

create table if not exists bookCategories
(
book_category_text TEXT NOT NULL UNIQUE,
book_category_desc TEXT NOT NULL,
book_category_parent DEFAULT NULL CONSTRAINT fk_bkCategory_parent REFERENCES bookCategories(book_category_text)
);


------------------------------------------


create table if not exists userTypes
(
type INT PRIMARY KEY NOT NULL,
user_type_name TEXT NOT NULL
);

create table if not exists userStatus
(
active INT PRIMARY KEY NOT NULL,
user_status_name TEXT NOT NULL
);

create table if not exists allusers
(
uid TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
pwd TEXT DEFAULT "39dfa55283318d31afe5a3ff4a0e3253e2045e43" NOT NULL,
contact TEXT,
type INT NOT NULL DEFAULT 0 CONSTRAINT fk_admin_type REFERENCES userTypes(type),
active INT NOT NULL DEFAULT 0 REFERENCES userStatus(active),
create_date TEXT,
revoke_date TEXT
);

create trigger if not exists trigger_date_user AFTER INSERT ON allusers for each row
BEGIN
	UPDATE allusers SET create_date = date('now','localtime') WHERE rowid=new.rowid;
	UPDATE allusers SET revoke_date = date('now','localtime', '+1 year') WHERE rowid=new.rowid AND revoke_date ISNULL;
END;

create view if not exists users AS SELECT * FROM allusers WHERE type=0;

create view if not exists admins AS SELECT * FROM allusers WHERE type>0;

create table if not exists finement
(
uid TEXT PRIMARY KEY NOT NULL REFERENCES allusers(uid) ON DELETE CASCADE,
finement NUM NOT NULL DEFAULT 0
);

-----------------------------------------

create table if not exists sessions
(
uid TEXT UNIQUE CONSTRAINT fk_uid REFERENCES allusers (uid) ON DELETE CASCADE,
sid TEXT NOT NULL UNIQUE,
type INT REFERENCES userTypes (type) ON DELETE CASCADE,
ip TEXT,
data TEXT,
login_datetime TEXT,
last_active TEXT
);

create trigger if not exists trigger_login_datetime AFTER INSERT ON sessions for each row
BEGIN
	UPDATE sessions SET login_datetime = datetime('now','localtime'), last_active = datetime('now','localtime') WHERE rowid=new.rowid ;
	DELETE FROM sessions WHERE last_active < datetime('now','localtime','-30 minute');
END;

create trigger if not exists trigger_login_history AFTER DELETE ON sessions for each row WHEN old.uid NOTNULL
BEGIN
	INSERT INTO sessionsHistory (uid, type, ip, login_datetime, logout_datetime) VALUES ( old.uid, old.type, old.ip, old.login_datetime, datetime('now','localtime') ) ;
END;

create table if not exists sessionsHistory
(
uid TEXT NOT NULL REFERENCES allusers (uid) ON DELETE CASCADE,
type INT NOT NULL REFERENCES userTypes (type) ON DELETE CASCADE,
ip TEXT,
login_datetime TEXT,
logout_datetime TEXT
);

create table if not exists borrows
(
serial TEXT NOT NULL CONSTRAINT fk_borrow_bk_serial REFERENCES bookInstances (serial),
uid TEXT NOT NULL CONSTRAINT fk_borrow_uid REFERENCES allusers (uid),
admin TEXT CONSTRAINT fk_borrow_admin REFERENCES allusers (uid),
borrow_datetime TEXT,
due_datetime TEXT 
);

create trigger if not exists trigger_borrow_datetime AFTER INSERT ON borrows for each row BEGIN
	UPDATE borrows SET borrow_datetime = datetime('now','localtime') WHERE rowid=new.rowid;
	UPDATE borrows SET due_datetime = datetime('now','localtime', '+30 day') WHERE rowid=new.rowid AND due_datetime ISNULL;
END;

create table if not exists borrowsHistory
(
serial TEXT NOT NULL REFERENCES bookInstances (serial) ON DELETE CASCADE,
uid TEXT NOT NULL REFERENCES allusers (uid) ON DELETE CASCADE,
borrow_admin TEXT NOT NULL REFERENCES allusers (uid) ON DELETE CASCADE,
borrow_datetime TEXT NOT NULL,
return_admin TEXT NOT NULL REFERENCES allusers (uid) ON DELETE CASCADE,
return_datetime TEXT
);
create trigger if not exists trigger_borrow_return AFTER INSERT ON borrowsHistory for each row BEGIN UPDATE borrowsHistory SET return_datetime = datetime('now','localtime') WHERE rowid=new.rowid;END;

------------------------------------------------

create table if not exists holidays
(
holiday_start TEXT NOT NULL,
holiday_end TEXT NOT NULL
);

'''

    sql_default_settings='''
insert or ignore into userStatus ( active, user_status_name ) VALUES ( 0, "启用" );
insert or ignore into userStatus ( active, user_status_name ) VALUES ( 1, "禁用" );

insert or ignore into userTypes ( type, user_type_name ) VALUES ( 0, "普通用户" );
insert or ignore into userTypes ( type, user_type_name ) VALUES ( 1, "管理员");
insert or ignore into userTypes ( type, user_type_name ) VALUES ( 2, "图书管理员");
insert or ignore into userTypes ( type, user_type_name ) VALUES ( 3, "系统管理员");

insert or ignore into allusers (uid, name, type) VALUES ("00000000", "User", 0);
insert or ignore into allusers (uid, name, type) VALUES ("admin1", "Admin", 1);
insert or ignore into allusers (uid, name, type) VALUES ("admin2", "Book Admin", 2);
insert or ignore into allusers (uid, name, type) VALUES ("admin3", "Sys Admin", 3);
'''
    print "Initializing Database"
    import sqlite3
    conn=sqlite3.connect(my_database)
    conn.executescript(sql_schema)
    conn.executescript(sql_default_settings)
    conn.commit()
    conn.close()
# }}}
# imports {{{
import cgitb; cgitb.enable(display=my_cgitb_display, logdir=my_logdir)
import cgi
from HTMLgen import *
import datetime
import hashlib
import Cookie
import os
# }}}
# Permissions {{{
user_allowed_modules = {}
user_allowed_modules[-1] = [ 'guest_welcome', 'search_book', 'login', 'about', ]
user_allowed_modules[0] = [ 'user_welcome', 'user_status', 'user_renew', 'passwd', 'search_book', 'logout', ]
user_allowed_modules[1] = [ 'user_welcome', 'admin_borrow', 'admin_return', 'admin_renew', 'admin_view_borrow_user', 'passwd', 'search_book', 'logout', ]
user_allowed_modules[2] = [ 'user_welcome', 'admin_add_book', 'admin_edit_book', 'admin_delete_book', 'admin_add_copy', 'admin_rm_copy', 'admin_define_category', 'passwd', 'search_book', 'logout', ]
user_allowed_modules[3] = [ 'user_welcome', 'admin_add_user', 'admin_batch_add_user', 'admin_edit_user', 'admin_rm_user', 'admin_clear_finement', 'admin_define_holiday', 'admin_view_users', 'admin_view_borrows', 'passwd', 'search_book', 'author', 'logout',]
# }}}
# database {{{
import sqlite3
assert sqlite3.sqlite_version > 3.7

# connection to sqlite3 database
# autocommit mode, with auto rollback/commit inside context manager (`with')
class myConnection(sqlite3.Connection):
    def __enter__(self):
        self.isolation_level="DEFERRED"
        return sqlite3.Connection.__enter__(self)

    def __exit__(self, t, v, tb):
        rt = sqlite3.Connection.__exit__(self, t, v, tb)
        self.isolation_level=None
        return rt

    def insert_sql(self, table, columns):
        sql='INSERT INTO ' + table + ' (' + ','.join(columns) + ') VALUES (' + ','.join( ['?']*len(columns) ) + ')'
        return sql

    def insert(self, table, columns, args):
        if not isinstance(columns, list) and not isinstance(columns, tuple):
            columns = (columns,)
        if not isinstance(args, list) and not isinstance(args, tuple):
            args = (args,)
        sql = self.insert_sql(table, columns)
        return self.execute(sql, args)

    def insert_dict(self, table, fields):
        columns = fields.keys()
        args = [ fields[key] for key in columns ]
        return self.insert(table, columns, args)

    def select_sql(self, table, output_columns, predicate_columns, limit=0, like=False):
        if like:
            compare = ' like '
        else:
            compare = ' == '
        sql = 'SELECT ' + ','.join( output_columns ) + ' FROM ' + table
        if len(predicate_columns):
            sql += ' WHERE ' +  ' and '.join( [ col + compare + '(?) ' for col in predicate_columns ] )
        if limit:
            sql +=' LIMIT (?)'
        return sql

    def select(self, table, output_columns, predicate_columns=(), args=(), limit=0, like=False):
        if isinstance(output_columns, str): output_columns = ( output_columns, )
        if not isinstance(predicate_columns, list) and not isinstance(predicate_columns, tuple):
            predicate_columns = (predicate_columns,)
        if not isinstance(args, list) and not isinstance(args, tuple):
            args = (args,)
        sql = self.select_sql(table, output_columns, predicate_columns, limit=limit, like=like)
        if limit:
            args.append(limit)
        return self.execute(sql, args)

    def select_dict(self, table, output_columns, predicate, limit=0, like=False):
        columns = predicate.keys()
        args = [ predicate[key] for key in columns ]
        return self.select(table, output_columns, columns, args, limit=limit, like=like)

def create_database_connection():
    assert os.access(my_database, os.R_OK|os.W_OK)
    global conn
    conn=sqlite3.connect(my_database, factory=myConnection)
    conn.isolation_level = None
    conn.text_factory = str
    conn.execute('PRAGMA foreign_keys = ON;')
    conn.execute('PRAGMA temp_store = MEMORY;')
# }}}
#  myForm {{{
# myControls {{{
def genTextarea():
    def __gen_textarea__(name, value='', **kw):
        textarea = Textarea(name=name, text=value, **kw)
        return textarea
    return __gen_textarea__
def genCheckbox():
    def __gen_checkbox__(name, value=1, **kw):
        checkbox = Input(name=name, type='checkbox', checked=bool(value), **kw)
        return checkbox
    return __gen_checkbox__
def genRadio(dict_options):
    def __gen_radio__(name, value=None, **kw):
        div=Div()
        for _val, _label in dict_options:
            radio = Input(name=name, type='radio', value=_val, checked=(value==_val), rlabel=_label, **kw)
            div.append(radio)
        return div
    return __gen_radio__
def genSelect(dict_options):
    def __gen_select__(name, value="", **kw):
        return Select(dict_options, name=name, selected=(value,), **kw)
    return __gen_select__
def genLabel( append_hidden_input=False ):
    def __gen_label__(name, value="", label_value=None, **kw):
        div=Div()
        if append_hidden_input:
            div.append(Input(type="hidden", name=name, value=value) )
        if label_value is None: label_value = value
        div.append(label_value)
        return div
    return __gen_label__
# }}}
# myFormEntry {{{
class myFormEntry():
    def __init__(self, **kw):
        # required attributes
        self._args_ = {
            'name' : '',
            'label' : 'DEFAULT_LABEL',
            'required' : False,
            'fixed': False,
            'generator' : Input,
            'convert' : None,
            'db_column' : None,

            'check_basic' : True,
            'check_format' : True,
            'check_db' : False,

            'check' : None,
            '_db_check_' : None,
            '_basic_check_' : None,
            '_format_check_' : None,
        }
        # other attributes, will be passed to the form entry generator
        self.params={}
        # set attributes, with default values
        self.setup(init=True, **kw)
        self.created=None

    def setup(self, init=False, **kw):
        if init:
            self.__dict__.update(self._args_)
        for key in self._args_.keys():
            if key in kw.keys():
                val = kw[key]; del kw[key]
                setattr(self, key, val)
        self.params.update(kw)

    # create a read only form entry
    def fix(self, value=None, label_value=None):
        self.generator = genLabel( append_hidden_input = self.required )
        if value is not None: self.params['value']=value
        if label_value is not None: self.params['label_value']=label_value
        self.required = False

    def _check( self, check_func, val, error_suffix='错误', conv=False):
        if check_func is None: return val
        if not callable(check_func):
            check_func, error_suffix = check_func
        error_desc = self.label + error_suffix
        rt_val = val
        try:
            if conv:
                rt_val = check_func( val )
            else:
                assert check_func( val )
        except Exception, e:
            rt_val = False
            self._list_error_desc.append( error_desc )
        return rt_val

    def get_value(self, list_error_desc = None):
        if list_error_desc == None:
            list_error_desc = []
        self._list_error_desc = list_error_desc
        val = session.get_value(self.name)
        if val is None or not val.strip():
            if self.required:
                self._list_error_desc.append( self.label + '不能为空' )
                return False
            else: return None
        val = val.strip()
        val = self._check( self.convert, val, conv=True )
        val = self._check( self.check, val )
        if self.check_basic: val = self._check( self._basic_check_, val, '错误' )
        if self.check_format: val = self._check( self._format_check_, val, '格式错误')
        if self.check_db: val = self._check( self._db_check_, val, '不存在' )

        return val

    def create(self):
        self.created = self.generator(name=self.name, **self.params)
    def __str__(self):
        if self.fixed: self.fix()
        if not self.created: self.create()
        return str(self.created)
# }}}
# myBaseForm {{{
class myBaseForm():
    def __init__(self, cgi, value='确定', name='submit'):
        self.submit_name=name
        self.submit_value=value
        self.name = "form_" + name
        self.cgi = cgi
        # fields currently in this form
        self.fields=[]
        # fields currently in this form, indexed by field_def
        self.fields_available={}
        # predefined fields
        self.fields_predefined={}
        # addon elements to the form, e.g. java scripts
        self.addons=[]
        self._perform_continue=False
        self._retain_continue=False

    # define a form entry, indexed by field_def
    def define( self, field_def, **kw ):
        if 'name' not in kw.keys():
            kw['name']=field_def
        self.fields_predefined[ field_def ] = kw

    # append a form entry to this form, predefined or not.
    def append( self, field_def, **kw ):
        defined = self.fields_predefined.get( field_def , { 'name': field_def } )
        entry = myFormEntry( **defined )
        entry.setup( **kw )
        
        self.fields_available[ field_def ] = entry
        self.fields.append( entry )
        return self

    def appendlist( self, defs ):
        for item in defs:
            if isinstance( item, str ):
                self.append( item )
            else:
                field_def, kw = item
                self.append( item, **kw )
        return self

    def get(self, name):
        return self.fields_available.get(name)

    def db_columns(self):
        return [ p.db_column or p.name for p in self.fields if p.db_column is not False ]

    # set default value for the entries currently in this form
    def set_defaults(self, defaults):
        for k in defaults.keys():
            if not k: continue
            for entry in self.fields:
                if entry.name == k: entry.params['value']=defaults[k]

    def set_focus(self, to_focus):
        script = Script(code='document.%s.%s.focus();' % (self.name, to_focus) )
        self.addons.append( script )

    def perform_continue(self, toggle=True):
        self._perform_continue = toggle

    def retain_continue(self, toggle=True):
        self._retain_continue = toggle

    def __str__(self):
        form=Form( cgi=self.cgi, name=self.name, submit = Input(type="submit", value=self.submit_value, name=self.submit_name) )
        tab=TableLite(cellspacing=4)
        for entry in self.fields:
            label = entry.label
            if label: label += ':'
            tr = TR( TD( label ),  TD( entry ) )
            if entry.required:
                tr.append( TD ( Span( '*', style='color: red;' ) ) )
            tab.append(tr)
        form.append(tab)
        for addon in self.addons: form.append(addon)

        cont_mod = session.get_value('continue')
        if self._perform_continue and cont_mod:
            form.append( Input(type='hidden', name='_form_continue', value=cont_mod) )
        if self._retain_continue and cont_mod:
            form.append( Input(type='hidden', name='continue', value=cont_mod) )

        return str(form)

    def get_input(self, check_submit_value=True, for_database=False):
        form_input = {}
        if session.get_value(self.submit_name) == self.submit_value or not check_submit_value:
            for entry in self.fields:
                list_error_desc = []
                val = entry.get_value( list_error_desc )
                if val == None and not entry.required: continue
                if isinstance(val,bool) and val == False:
                    raise ValueError( list_error_desc[0] )
                if not for_database:
                    form_input[entry.name] = val
                elif entry.db_column is not False:
                    key = entry.db_column or entry.name
                    form_input[key] = val
        return form_input
# }}}
# myForm {{{
class myForm(myBaseForm):
    def __init__(self, cgi, **kw):
        myBaseForm.__init__(self, cgi, **kw)
        self.define( "bookid", label="索书号",
                    _format_check_=lambda x: re.match('^[A-Z]{3}[0-9]{3}$', x), 
                    _db_check_ = lambda bookid: conn.select('books', 'bookid', 'bookid', bookid).fetchone(),
                    convert=lambda x:x.upper(),
                   )
        self.define( "title", label="书名", )
        self.define( "author", label="作者")
        self.define( "publisher", label="出版社")
        self.define( "isbn", label="ISBN13",
                    _format_check_=lambda x: re.match('^[0-9]{13}$', x) and 
                                        (-sum( [ ((i%2)==0 and 1 or 3) * int(x[i]) for i in range(12) ] )) % 10 == int(x[12]), 
                   )
        self.define( "price", label="价格", _basic_check_=lambda x: float(x) > 0)
        self.define( "quantity", label="复本数", _basic_check_=lambda x: int(x) >=0, convert=int)
        self.define( "serial", label="书号", 
                    _format_check_=lambda x: re.match('^[0-9]{6}', x),
                    _db_check_=lambda serial: conn.select('bookInstances', 'serial', 'serial', serial).fetchone(),
                   )
        self.define( "contact", label="联系方式")

        user_types = conn.execute('SELECT user_type_name, type FROM userTypes;').fetchall()
        self.define( "type", label="用户类型", convert=lambda x:int(x), generator = genSelect ( user_types ))

        self.define( "revoke_date", label="有效期至", _format_check_ = lambda x: datetime.date( *map(int, x.split('-'))) )
        self.define( "date", label="日期", value='0000-00-00', _format_check_ = lambda x: datetime.date( *map(int, x.split('-'))) )

        active_status = conn.execute('SELECT active, user_status_name FROM userStatus;').fetchall()
        self.define( 'active', label='状态', generator = genRadio( active_status ), convert=int )
        self.define( 'circulatable', label='状态', value=1, convert=int, generator = genRadio( [(1,'流通'), (0,'不流通')] ) )

        def _override_circulatable_converter(x):
            x = int(x)
            if x < 0: return None
            return x
        self.define( 'override_circulatable', label='状态', value=-1, convert=_override_circulatable_converter, generator = genRadio( [(-1,'默认值'), (1,'流通'), (0,'不流通')] ) )

        self.define( "name", label="姓名" )
        self.define( "uid", label="用户名", _db_check_ = lambda uid: conn.select('allusers', 'uid', 'uid', uid).fetchone() )
        self.define( "user_id", name='uid', db_column='uid', label="用户名", 
                    _db_check_ = lambda uid: conn.select('users', 'uid', 'uid', uid).fetchone() ,
                    _format_check_ = lambda x: re.match('^0[0-9]{7}$|1[0-9]{9}$', x),
                   )
        self.define( "admin_id", name='uid', db_column='uid', label="用户名", 
                    #_db_check_ = lambda uid: conn.select('admins', 'uid', 'uid', uid).fetchone() ,
                    #_basic_check_ = lambda x: re.match('^[A-Za-z]', x),
                   )
        self.define( "pwd", label="密码", type="password", convert=lambda pwd: hashlib.sha1(pwd).hexdigest(), )

        _check_parent = lambda x: re.match('^[A-Z]{0,2}$', x )
        _check_full = lambda x: re.match('^[A-Z]{3}$', x )
        _bk_ctgrs_all = [ ('_(无类别)', ''), ] + conn.execute('SELECT book_category_text || " " || book_category_desc, book_category_text FROM bookCategories ORDER BY book_category_text' ).fetchall() 
        _bk_ctgrs_full = filter( lambda x : _check_full(x[1]), _bk_ctgrs_all )
        _bk_ctgrs_parent = filter( lambda x : _check_parent(x[1]), _bk_ctgrs_all )
        self.define( "book_category", label="图书类别", value='', generator = genSelect( _bk_ctgrs_full ), _basic_check_ = _check_full, )
        self.define( "book_parent_category", label="图书类别", value='', generator = genSelect( _bk_ctgrs_parent ), _basic_check_ = _check_parent )

        _book_sub_categories = [ (chr(x), chr(x)) for x in range( ord('A'), ord('Z')+1 ) ]
        self.define( "book_sub_category" , label="子类别" , generator = genSelect( _book_sub_categories ),  _basic_check_ = lambda x: re.match('^[A-Z]$', x ),)

        self.define( "textarea", label="", generator = genTextarea() )
# }}}
# }}}

# myViewer {{{
class myViewerColumn():
    def __init__(self, name='', heading='', handler=None, data_cols=1, **kw):
        self.name=name
        self.heading=heading
        self.handler=handler
        self.kw=kw
        self.data_cols=data_cols

    def get_heading(self, suffix=''):
        td=TD( self.heading + suffix )
        for key in self.kw.keys():
            td.__setattr__( key, self.kw[key] )
        return td
        
    def get_cell(self, content):
        td=TD()
        def f(x):
            if x == None: x = ''
            return x
        content = map( f, content )
        if self.handler:
            content = self.handler( *content )
        else:
            content = content[0]
        td.append(content)
        for key in self.kw.keys():
            td.__setattr__( key, self.kw[key] )
        return td

class myViewer():
    def __init__(self):
        self.columns=[]
        self.columns_available={}

    def append(self, **kw):
        col = self.install(**kw)
        self.columns.append(col)

    def install(self, name='', **kw):
        kw['name'] = name
        col = myViewerColumn(**kw)
        self.columns_available[ name ] = col
        return col

    def get(self, name):
        return self.columns_available.get(name)

    def configure(self, names):
        self.columns = []
        for name in names:
            col = self.columns_available[ name ]
            self.columns.append( col )
        return self

    def table(self, data, has_head=True, **kw):
        num_cols = len(self.columns)
        tab=TableLite(**kw)
        if has_head:
            tr=TR()
            for i in range(num_cols):
                tr.append( self.columns[i].get_heading() )
            tab.append( tr )
        for row in data:
            tr=TR()
            j=0
            for i in range(num_cols):
                col = self.columns[i]
                data_cols=col.data_cols
                data = tuple(row[j:j+data_cols])
                j = j + data_cols
                tr.append( col.get_cell( data ) )
            tab.append(tr)
        return tab

    def table_vert(self, row, **kw):
        tab=TableLite(**kw)
        j=0
        for i in range(len(self.columns)):
            tr=TR()
            col = self.columns[i]
            data_cols=col.data_cols
            data = tuple(row[j:j+data_cols])
            j = j + data_cols
            tr.append( col.get_heading(':') )
            tr.append( TD( width=40 ) )
            tr.append( col.get_cell( data ) )
            tab.append(tr)
        return tab

# }}}

# modules {{{
benyue_modules={}
benyue_virtual_modules={}
# base module {{{
class mod_benyue_base:
    virtual = 0
    disabled = 0
    sql_prepare=''
    def __init__(self):
        if self.disabled or not self._check_enabled():
            self.disabled=1
        name=self.__class__.__name__
        self.name = name.replace('mod_','')
        if self.virtual:
            self.name = self.name.replace('virtual_','')
        if not self.disabled:
            self.prepare_datebase()

    def prepare_datebase(self):
        if self.sql_prepare:
            conn.executescript(self.sql_prepare)

    def _check_enabled(self):
        return True

class mod_virtual_benyue(mod_benyue_base):
    virtual = 1

class mod_benyue(mod_benyue_base):
    text=''
    title='__UNDEFINED__'
    virtual = 0
    def __init__(self):
        mod_benyue_base.__init__(self)
        self.url=my_location + "?act=" + self.name
        self.__info=[]
        self._session_data = ''
        if self.title == '__UNDEFINED__':
            self.title = self.text
        self._form_prepared = False
        if not self.disabled:
            self.prepare_forms()

    def succeed(self, desc):
        style="color: green; font-size:12"
        self.__add_info(desc, style)
    def fail(self, desc):
        style="color: red; font-size:12"
        self.__add_info(desc, style)
    def info(self, desc):
        style="color: grey; font-size:12"
        self.__add_info(desc, style)

    def __add_info(self, desc, style):
        if isinstance( desc , unicode ):
            desc=desc.encode('utf8')
        session.session_info.append( (desc, style) )

    def get_form_data(self, form, **kw):
        try:
            return form.get_input(**kw)
        except ValueError,e:
            self.fail(e.args[0])

    def act_main(self): pass
    def act(self):
        if not self._form_prepared:
            self.prepare_forms()
            self._form_prepared = True
        self.act_main()
        html=session.get_value('_form_continue')
        if html: session.current_act = html

    def html_main(self):pass
    def html(self):
        if not self._form_prepared:
            self.prepare_forms()
            self._form_prepared = True
        div=Div()
        if self.title:
            div.append( Heading(3, self.title) )
        for desc, style in session.session_info:
            div.append( Span( Text(desc), style=style ), BR() )
        if len(session.session_info):
            div.append( P() )
        main = self.html_main()
        if main: div.append( main )
        return div

    def prepare_forms(self): pass
# }}}

# virtual module: admin triggers {{{
class mod_virtual_admin( mod_virtual_benyue ):
    sql_prepare='''
create temp table adminCurrent
(
uid TEXT UNIQUE NOT NULL
);

create temp trigger trigger_borrow_admin AFTER INSERT ON borrows for each row BEGIN UPDATE borrows SET admin = ( SELECT uid FROM adminCurrent ) WHERE rowid=new.rowid; END;

create temp trigger trigger_borrow_history BEFORE DELETE ON borrows for each row
BEGIN
	INSERT INTO borrowsHistory (serial, uid, borrow_admin, borrow_datetime, return_admin, return_datetime) 
		VALUES ( old.serial, old.uid, old.admin, old.borrow_datetime,  ( SELECT uid FROM adminCurrent ), datetime('now','localtime') );
END;
'''

# }}}
# virtual module: finement {{{
class mod_virtual_finement( mod_virtual_benyue ):
    my_fine_rate = 0.2
    def calc_finement(self, uid):
        res = conn.select('finement', 'finement', 'uid', uid).fetchone() or (0,)
        fine = res[0]

        res = conn.select('borrows', 'due_datetime', 'uid', uid).fetchall()
        dates = [ item[0] for item in res ]
        return self.__calc_finement_date( fine, dates )

    def __calc_finement_date( self, current_finement, due_datetime ): 
        try:
            total_delay = 0
            if not isinstance(due_datetime, list) and not isinstance(due_datetime, tuple):
                due_datetime = (due_datetime,)
            for str_date in due_datetime:
                str_date = str_date.split(' ')[0]
                return_date = datetime.date(*map(int, str_date.split('-')))
                delta = datetime.date.today() - return_date
                delay = delta.days
                if delay > 0: total_delay += delay
            ret = current_finement + total_delay * self.my_fine_rate
        except Exception, e:
            session.debuginfo += 'EXCEPTION in __calc_finement_date: ' + repr(e)
            ret = current_finement
        return ret

    __sql_trigger='''
create temp trigger trigger_calc_finement_on_return BEFORE DELETE ON borrows for each row
BEGIN
    INSERT OR IGNORE INTO finement (uid, finement) VALUES (old.uid, 0);
	UPDATE finement SET finement = calc_finement( finement, old.due_datetime ) WHERE uid=old.uid;
    DELETE FROM finement WHERE finement = 0;
END;
'''
    def prepare_datebase(self):
        conn.create_function('calc_finement', 2, self.__calc_finement_date) 
        conn.executescript(self.__sql_trigger)

# }}}
# virtual module: view_borrows {{{
class mod_virtual_view_borrows(mod_virtual_benyue):
    def get_viewer(self, admin=False, renewal=True):
        viewer=myViewer()
        viewer.install( name='bookid',  heading="索书号", width=80 , handler = lambda bookid: Href( text=bookid, url=get_mod('search_book').url + '&bookid=' + bookid )  )
        viewer.install( name='title',  heading="书名", width=300 )
        viewer.install( name='author',  heading="作者", width=100 )
        viewer.install( name='uid',  heading="用户名", width=100 )
        viewer.install( name='name',  heading="姓名", width=100 )
        viewer.install( name='borrow_datetime',  heading="借书日期", width=150, handler = lambda x: x.split(' ')[0] )
        viewer.install( name='due_datetime',  heading="应还日期", width=150, handler = lambda x: x.split(' ')[0] )
        viewer.install( name='serial',  heading='', width=30, )
        return viewer

    def list_borrows_admin(self):
        sql = '''SELECT books.bookid, books.title, users.uid, users.name, borrows.serial, borrows.borrow_datetime FROM 
                    borrows JOIN bookInstances USING (serial)
                    JOIN books USING (bookid)
                    JOIN users USING (uid);
                    '''
        fields = 'bookid title uid name serial borrow_datetime'
        viewer = self.get_viewer()
        data = conn.execute(sql)
        viewer.configure( fields.split(' ') )
        return viewer.table(data)

    def list_borrows_user(self, uid=None):
        if not uid:
            uid=session.uid
        data = conn.execute('SELECT books.bookid, books.title, books.author, borrows.borrow_datetime, borrows.due_datetime, borrows.serial FROM bookInstances JOIN books USING (bookid) JOIN borrows USING(serial) WHERE borrows.uid=(?)', (uid,)).fetchall()
        fields = 'bookid title author borrow_datetime due_datetime'.split(' ')

        viewer = self.get_viewer()
        for m in ( 'user_renew', 'admin_renew' ):
            if m in session.allowed_modules and has_mod(m):
                # use `closure' to fix the url
                _get_handler = lambda _url_prefix: ( lambda serial: Href( text='续借', url = _url_prefix + '&serial=' + serial ) )
                viewer.get('serial').handler = _get_handler( get_mod(m).url + '&uid=' + uid )
                fields.append('serial')
                break
        viewer.configure( fields )
        tab = viewer.table(data)
        return tab

# }}}
# virtual module: view_books {{{
class mod_virtual_view_books(mod_virtual_benyue):
    def get_book_info( self, bookid, columns=() ):
        if not len(columns): return []
        row = conn.select_dict( "books", columns, { 'bookid': bookid } ).fetchone()
        serials = conn.execute('SELECT serial FROM bookInstances WHERE bookid=(?)', (bookid,)).fetchall()
        serials = [ s[0] for s in serials ]
        quantity = len(serials)
        row += (quantity,) 
        if session.user_type > 0:
            row += ( ','.join(serials), )
        return row

    def get_viewer_book(self):
        viewer = myViewer()
        viewer.install( name='title', heading='书名', )
        viewer.install( name='author', heading='作者', )
        viewer.install( name='publisher', heading='出版社', )
        viewer.install( name='isbn', heading='ISBN', )
        viewer.install( name='price', heading='价格', )
        viewer.install( name='circulatable', heading='可流通', handler = lambda x: x and '流通' or '不流通' )
        viewer.install( name='quantity', heading='复本数', )
        viewer.install( name='serials', heading='复本' )
        return viewer
    
    def get_viewer_book_detail(self):
        if 'admin_edit_book' in session.allowed_modules:
            handler = lambda bookid: Href( text=bookid, url=get_mod('admin_edit_book').url + '&setbookid=' + bookid + '&continue=' + self.name)
        else:
            handler = None
        viewer = self.get_viewer_book()
        viewer.install( name='bookid', heading='索书号', handler = handler )
        return viewer

    def get_viewer_book_list(self):
        viewer = self.get_viewer_book()
        viewer.install( name='bookid', heading='索书号', handler = lambda bookid: Href( text=bookid, url=get_mod('search_book').url + '&bookid=' + bookid ) )
        return viewer

    def html_book_info( self, bookid ):
        query_columns = [ 'bookid', 'title', 'author', 'publisher', 'isbn', 'price', ]
        result = self.get_book_info( bookid, query_columns )

        columns = query_columns
        columns.append('quantity')
        if session.user_type > 0:
            columns.append('serials')
        viewer = self.get_viewer_book_detail().configure( columns )
        tab = viewer.table_vert(result)
        return tab
    
    def html_book_info_list(self, book_ids):
        query_columns= [ 'bookid', 'title', 'author', 'publisher', 'circulatable', ]
        results = [ self.get_book_info(bookid, query_columns) for bookid in book_ids ]

        columns = query_columns
        columns.append('quantity')
        if session.user_type > 0:
            columns.append('serials')
        viewer = self.get_viewer_book_list().configure( columns )
        tab = viewer.table( results, cellspacing=10, width="100%" )
        return tab
    
    def html_book_copies_info_list(self, bookid):
        book_status = conn.select( 'books', 'circulatable', 'bookid', bookid ).fetchone()[0]
        book_status = book_status and '流通' or '不流通'
        viewer = myViewer()
        viewer.append( heading = '复本' )
        viewer.append( heading = '状态' , data_cols=2, handler = lambda x,y: x and '已借出' or ( y=='' and book_status or (y and '流通' or '不流通')) )
        viewer.append( heading = '借阅者' , data_cols=2, handler = lambda x,y: x + (y and (' (' +  y + ')')), )
        viewer.append( heading = '借阅日期', handler = lambda x: x.split(' ')[0] )
        viewer.append( heading = '应还日期', handler = lambda x: x.split(' ')[0] )
        columns = [ 'serial', 'uid', 'override_circulatable', 'name', 'uid', 'borrow_datetime', 'due_datetime' ]
        table = '''books JOIN bookInstances USING (bookid) 
                    LEFT JOIN borrows USING(serial) LEFT JOIN allusers USING(uid)'''
        data = conn.select( table, columns, 'books.bookid', bookid )
        tab = viewer.table( data, width='100%' )
        return tab

# }}}
# {{{ virtual module: borrows
class mod_virtual_borrows( mod_virtual_benyue ):
    def _calc_due_datetime(self, temp=False):
        if temp:
            _d = datetime.date.today() + datetime.timedelta(1,0,0)
            return _d.isoformat() + ' 23:59:59'
        t = datetime.datetime.now() + datetime.timedelta(30,0,0)
        t = t.strftime('%Y-%m-%d %H:%M:%S')
        q = conn.execute('SELECT holiday_end FROM holidays WHERE holiday_start <= (?) AND holiday_end > (?)', (t,t,) ).fetchall()
        if not len(q):
            return t
        else:
            return max( [ x[0] for x in q ] ) + ' 23:59:59'
    def borrow(self, uid, serial, temp=False):
        q=conn.execute('SELECT bookid FROM bookInstances WHERE serial=(?)', (serial,) ).fetchone()
        if not q:
            return self.fail('查无此书')
        else:
            book_id=q[0]
            q=conn.execute('SELECT title FROM books WHERE bookid=(?)', (book_id,) ).fetchone()
            book_name=q[0]
        q=conn.execute('SELECT name FROM users WHERE uid=(?)', (uid,) ).fetchone()
        if not q:
            return self.fail('查无此人')
        else:
            user_name=q[0]
        q=conn.execute('SELECT count(*) FROM borrows WHERE serial=(?)', (serial,) ).fetchone()[0]
        if q > 0: return self.fail('此书已借出')
        q=conn.execute('SELECT min(due_datetime) FROM borrows WHERE uid=(?)', (uid,) ).fetchone()[0]
        if q and q < datetime.date.today().strftime('%Y-%m-%d'):
            return self.fail('有过期图书未还')

        if not temp:
            q=conn.execute('SELECT count(*) FROM borrows WHERE uid=(?) and date(borrow_datetime,"+1 day") < date(due_datetime)', (uid,) ).fetchone()[0]
            if q >= self.my_max_borrow: return self.fail('借书数量已达上限')
        q=conn.execute('SELECT count(*) FROM borrows WHERE uid=(?)', (uid,) ).fetchone()[0]
        if q >= self.my_max_borrow + self.my_max_temp_borrow: return self.fail('借书数量已达上限')

        q=conn.execute('SELECT finement FROM finement WHERE uid=(?)', (uid,) ).fetchone()
        if q and float(q[0]) > self.my_max_finement:
            return self.fail('欠款太多，不能借书')
        due_datetime = self._calc_due_datetime(temp)
        conn.execute('INSERT INTO borrows (serial, uid, due_datetime) VALUES (?,?,?)', (serial,uid,due_datetime))
        self.succeed('借出: ' + book_name + "  " + user_name + " (" + uid + "), " + due_datetime.split(' ')[0] )

    def renew(self, uid, serial, is_admin=True):
        q=conn.execute('SELECT date(borrow_datetime), date(due_datetime) FROM borrows WHERE serial=(?) and uid=(?)', (serial, uid) ).fetchone()
        if not q:
            return self.fail('没有借这本书')
        rt_date = datetime.date(*map(int,q[1].split('-')))
        br_date = datetime.date(*map(int,q[0].split('-')))
        if ( rt_date - br_date ).days < 10: return self.fail('临时借阅不能续借')
        if rt_date < datetime.date.today():
            return self.fail('已过期，不能续借')
        due_datetime = self._calc_due_datetime()
        conn.execute("UPDATE borrows SET due_datetime = (?) WHERE serial = (?)", ( due_datetime, serial ) )
        if is_admin:
            book_id=conn.execute('SELECT bookid FROM bookInstances WHERE serial=(?)', (serial,) ).fetchone()[0]
            book_name=conn.execute('SELECT title FROM books WHERE bookid=(?)', (book_id,) ).fetchone()[0]
            user_name=conn.execute('SELECT name FROM users WHERE uid=(?)', (uid,) ).fetchone()[0]
            self.succeed('续借: ' + book_name + "  " + user_name  + " (" + uid + "), " + due_datetime.split(' ')[0] )
        else:
            self.succeed('已续借至' + due_datetime.split(' ')[0])
# }}}

# module: unauthorized {{{
class mod_unauthorized( mod_benyue ):
    text='unauthorized'
    def html(self):
        return Div(Text('UNAUTHORIZED'))
# }}}
# module: unavailable {{{
class mod_unavailable( mod_benyue ):
    text='unavailable'
    def html(self):
        return Div(Text('NOT IMPLEMENTED'))
# }}}

# module: guest_welcome {{{
class mod_guest_welcome( mod_benyue ):
    text='首页'
    def html(self):
        path='welcome.html'
        if os.access(path, os.R_OK):
            page=open(path, 'r').read()
            return RawText(page)
        else:
            return Div(Text('欢迎'))
# }}}

# module: user_welcome {{{
class mod_user_welcome( mod_benyue ):
    text='首页'
    def html(self):
        uid = session.uid
        if not uid: return '' 
        div = Div(Text(''))
        q = conn.select('allusers', 'name', 'uid', uid).fetchone()
        if not q: return ''
        text='欢迎, ' + q[0]
        div.append(Text(text))
        return div
# }}}

# module: login {{{
class mod_login( mod_benyue ):
    text='登录'
    def prepare_forms(self):
        form=myForm( self.url )
        form.appendlist( ['uid', 'pwd'] )
        form.set_focus('uid')
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        uid=form_data.get('uid')
        pwd=form_data.get('pwd')
        if not uid or not pwd:  return
        q = conn.execute( 'SELECT type, revoke_date, active FROM allusers WHERE uid=(?) and pwd=(?)', (uid, pwd) ).fetchone()
        if q:
            user_type, revoke_date, active = q
            if user_type != 0 and my_admin_port:
                if os.environ.get('SERVER_PORT') != str(my_admin_port):
                    return self.fail("仅限普通用户使用")
            today = datetime.date.today()
            revoke_date = datetime.date( *map(int, revoke_date.split('-')) )
            if today > revoke_date:
                return self.fail("帐号已过期")
            if active != 0:
                return self.fail("帐号已禁用")
            session.login( uid, user_type )
        else:
            self.fail("登录失败")
    
    def html_main(self):
        return self.form1
# }}}
# module: logout {{{
class mod_logout( mod_benyue ):
    text='退出'
    def act_main(self):
        session.logout()
# }}}

# module: user_renew {{{
class mod_user_renew( mod_benyue, mod_virtual_borrows, mod_virtual_view_borrows ):
    text=''
    title='续借'
    def act(self):
        uid=session.uid
        serial=session.get_value('serial')
        if not serial: return
        self.renew(uid, serial)

    def html_main(self):
        return self.list_borrows_user()

# }}}
# module: user_status {{{
class mod_user_status( mod_benyue, mod_virtual_view_borrows ):
    text='状态'
    def html_main(self):
        div=Div()
        div.append(self.list_borrows_user())
        div.append( BR(),BR(), "欠款:", get_virtual_mod('finement').calc_finement(session.uid) )
        return div
# }}}
# module: passwd {{{
class mod_passwd( mod_benyue ):
    text='修改密码'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( 'pwd', name="pwd0", label="旧密码", required=1)
        form.append( 'pwd', name="pwd1", label="新密码", required=1)
        form.append( 'pwd', name="pwd2", label="重复新密码", required=1)
        self.form1=form

    def act_main(self):
        uid = session.uid
        form_data = self.get_form_data(self.form1)
        if not form_data: return

        pwd0 = form_data['pwd0']
        pwd1 = form_data['pwd1']
        pwd2 = form_data['pwd2']

        pwd = conn.select('allusers', 'pwd', 'uid', uid).fetchone()[0]
        if pwd != pwd0:
            return self.fail("密码错误")

        if pwd1 != pwd2:
            return self.fail("两次输入的新密码不同")
        conn.execute('UPDATE allusers SET pwd=(?) WHERE uid=(?);', (pwd1, uid) )
        self.succeed("密码已修改")

    def html_main(self):
        return self.form1
# }}}

# module: search_book {{{
class mod_search_book( mod_benyue, mod_virtual_view_books ):
    text='查询图书'
    def prepare_forms(self):
        form = myForm(self.url, name="search")
        form.append( "bookid", check_format=0 )
        form.append( "title", check_format=0)
        form.append( "author", check_format=0)
        form.append( "publisher", check_format=0)
        form.append( "isbn", check_format=0)
        self.form1=form

    def search(self, search_keys=None, limit=1000):
        if search_keys == None: search_keys = {}
        keys=search_keys.keys()
        if len(keys) == 0: return []
        args=[]
        for key in keys:
            val= search_keys[key]
            if key not in ( "bookid", ):
                val = '%' + val + '%'
            else:
                val = val + '%'
            args.append(val)
        q=conn.select( "books", ('bookid',), keys, args, limit, like=True ).fetchall()
        book_ids = [ b[0] for b in q ]
        return book_ids

    def html_main(self):
        div=Div()
        search_keys = self.get_form_data(self.form1, check_submit_value=False)
        if search_keys:
            book_ids = self.search(search_keys)
            if session.get_value('search') and len(book_ids) != 1:
                div.append( self.html_book_info_list(book_ids), BR() )
            else:
                bookid = book_ids[0]
                div.append( self.html_book_info(bookid), BR() )
                if session.uid:
                    div.append( self.html_book_copies_info_list(bookid), BR() )
                    if 'admin_add_copy' in session.allowed_modules:
                        div.append( Href( text='添加复本', url=get_mod('admin_add_copy').url + '&setbookid=' + bookid + '&continue=' + self.name) )
        else:
            div.append( self.form1 )
        return div

# }}}

# module: admin_view_borrow_user {{{
class mod_admin_view_borrow_user( mod_benyue, mod_virtual_view_borrows ):
    text='查看借书'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "user_id", required=1, check_db=1 )
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        self._session_data = form_data.get('uid')
    def html_main(self):
        div=Div(self.form1)
        uid = self._session_data
        if uid: div.append(self.list_borrows_user(uid))
        return div
# }}}
# module: admin_borrow {{{
class mod_admin_borrow( mod_benyue, mod_virtual_borrows ):
    text='借书'
    my_max_borrow=3
    my_max_temp_borrow=3
    my_max_finement=5
    def prepare_forms(self):
        form = myForm(self.url)
        form.append('user_id', required=1, check_db=1)
        form.append('serial', required=1, check_db=1)
        form.append('_temp_borrow_', label='', db_column=False, type='checkbox', rlabel='临时借阅')
        form.set_focus('user_id')
        self.form1=form

    def act_main(self):
        form_data = self.get_form_data( self.form1 )
        if not form_data: return
        serial=form_data.get('serial')
        userid=form_data.get('uid')
        temp=form_data.get('_temp_borrow_')
        self.borrow(userid, serial, temp=temp)

    def html_main(self):
        form=self.form1
        return form

# }}}
# module: admin_return {{{
class mod_admin_return( mod_benyue ):
    text='还书'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append('serial', required=1,)
        form.set_focus('serial')
        self.form1=form

    def act_main(self):
        form_data = self.get_form_data( self.form1 )
        if not form_data: return
        serial=form_data.get('serial')
        q=conn.execute('SELECT uid FROM borrows WHERE serial=(?)', (serial,) ).fetchone()
        if not q: return self.fail('此书未借出')
        userid=q[0]
        book_id=conn.execute('SELECT bookid FROM bookInstances WHERE serial=(?)', (serial,) ).fetchone()[0]
        book_name=conn.execute('SELECT title FROM books WHERE bookid=(?)', (book_id,) ).fetchone()[0]
        user_name=conn.execute('SELECT name FROM users WHERE uid=(?)', (userid,) ).fetchone()[0]
        conn.execute('DELETE FROM borrows WHERE serial=(?)', (serial,) )
        self.succeed('还书: ' + book_name + "  " + user_name + ' (' + userid + ')' )

    def html_main(self):
        form=self.form1
        return form

# }}}
# module: admin_renew {{{
class mod_admin_renew( mod_benyue, mod_virtual_borrows, mod_virtual_view_borrows ):
    text='续借'
    def act(self):
        serial=session.get_value('serial')
        userid=session.get_value('uid')
        if not serial or not userid: return
        self.renew(userid, serial)

    def html_main(self):
        form=Form( cgi=self.url, name='form_return', submit = Input(type="submit", value="续借") )
        tab=TableLite(
                TR( TD("学号:"),  TD( Input( name="uid") )),
            )
        form.append(tab)
        div=Div(form)
        div.append( Script( code='document.form_return.uid.focus();' ) )
        uid = session.get_value('uid')
        if uid:
            div.append(self.list_borrows_user(uid))
        return div

# }}}

# module: admin_define_holiday {{{
class mod_admin_define_holiday( mod_benyue ):
    text='设定假期'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( 'date', name="holiday_start", label="开始日期", required=1)
        form.append( 'date', name="holiday_end", label="结束日期", required=1)
        self.form1=form

    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        s = form_data['holiday_start']
        e = form_data['holiday_end']
        if s > e: return self.fail('输入错误')
        with conn:
            conn.insert_dict( "holidays", form_data )
            conn.execute('UPDATE borrows SET due_datetime=(?) WHERE due_datetime >= (?) AND due_datetime <= (?)', ( e+' 23:59:59', s, e ) )
        self.succeed('设定成功')

    def html_main(self):
        div=Div()
        div.append(self.form1)

        viewer = myViewer()
        viewer.append( name='holiday_start', heading='开始日期', width=120)
        viewer.append( name='holiday_end', heading='结束日期', )

        today = datetime.date.today().strftime('%Y-%m-%d')
        data = conn.execute('SELECT * FROM holidays WHERE holiday_end >= (?) ORDER BY holiday_start', (today,) )
        tab = viewer.table( data )
        div.append(tab)
        return div
# }}}

# base module: admin_book {{{
class mod_admin_book( mod_benyue ):
    def admin_book_prepare_form(self):
        form = myForm(self.url)
        form.append( "bookid", required=1)
        form.append( "title", required=1, size=40)
        form.append( "author" , size=40)
        form.append( "publisher")
        form.append( "isbn" )
        form.append( "price" )
        form.append( "circulatable" )
        form.perform_continue()
        self.form_admin_book=form

    isbn_external='./searchISBN.sh'
    def isbn_prepare_form(self):
        if os.access(self.isbn_external, os.R_OK|os.X_OK):
            form = myForm(self.url, name="isbn_submit", value="查询ISBN")
            form.append( "isbn", required=1)
            form.set_focus('isbn')
            form.retain_continue()
            self.form_isbn=form
        else:
            self.form_isbn=None
    def act_isbn(self):
        form_data = self.get_form_data(self.form_isbn)
        if not form_data: return False
        isbn = form_data['isbn']
        import subprocess
        args = [self.isbn_external, isbn]
        child=subprocess.Popen(args, stdout=subprocess.PIPE)
        stdout, stderr = child.communicate()
        defaults={}
        if stderr: self.fail(stderr)
        for line in stdout.split('\n'):
            if not line: continue
            try:
                k,v = line.split(':', 1)
                defaults[k]=v
            except:
                self.fail('bad external output: ' + line)
        if not defaults:
            self.fail("没有结果")
            return True
        self.form_admin_book.set_defaults(defaults)
        self.form_admin_book.set_focus('bookid')
        return True

    def add_copy(self, bookid, quantity=1, serial=None, circulatable=None):
        year_prefix = datetime.date.today().strftime('%y')
        if not serial:
            sql = 'SELECT max(serial) FROM bookInstances WHERE serial LIKE "' + year_prefix + '%"'
            q = conn.execute(sql).fetchone()[0]
            serial = int( q or year_prefix + '0000' )
        for i in range(quantity):
            serial += 1
            conn.insert( "bookInstances", ('serial', 'bookid', 'override_circulatable'), (serial , bookid, circulatable) )
            self.succeed( bookid + " 新增复本,序列号: " + str(serial) )
# }}}
# module: admin_add_book {{{
class mod_admin_add_book( mod_admin_book ):
    text='录入新书'
    def prepare_forms(self):
        self.admin_book_prepare_form()
        self.form_admin_book.append( "quantity", check=lambda x: int(x) > 0, required=1, value=1 )
        self.isbn_prepare_form()
    
    def act_add_book(self):
        my_input = self.get_form_data(self.form_admin_book)
        if not my_input: return
        bookid = my_input["bookid"] or ''
        m = re.match('^(?P<c>[A-Za-z]*)(?P<n>[0-9]*)$', bookid)
        if not bookid or not m: return self.fail("索书号错误")
        if not conn.select('bookCategories', '*', 'book_category_text', m.group("c") ).fetchone():
            return self.fail("未定义的图书类别")
        my_input["book_category"]=m.group("c")
        my_input["book_num_in_category"]=m.group("n")
        quantity = my_input['quantity']
        del my_input['quantity']
        if conn.select_dict( "books", "count(*)", my_input ).fetchone()[0]:
            return self.fail("此书已存在")
        if conn.select_dict( "books", "count(*)", { 'bookid': my_input['bookid'], }).fetchone()[0]:
            return self.fail("索书号已存在")
        conn.insert_dict( "books", my_input )
        serial = int( conn.execute("SELECT max(serial) FROM bookInstances").fetchone()[0] or 100000 )
        self.succeed( my_input['title'] )
        self.add_copy(bookid, quantity)
        
    def act_main(self):
        if self.form_isbn:
            self.act_isbn()
        else:
            self.info("ISBN 录入方式未启用")
        self.act_add_book()

    def html_main(self):
        div=Div(self.form_admin_book)
        if self.form_isbn: div.prepend(self.form_isbn)
        return div
# }}}
# module: admin_edit_book {{{
class mod_admin_edit_book( mod_admin_book ):
    text='修改书目'
    def prepare_forms(self):
        self.admin_book_prepare_form()
        form = self.form_admin_book
        form.get('bookid').fix()
        form.get('bookid').check_db=1
        self.isbn_prepare_form()

        form = myForm(self.url, name="form_bookid")
        form.append( "bookid", name='setbookid', required=1, check_db=1)
        form.set_focus('setbookid')
        self.form_bookid = form

    def act_main(self):
        self.will_edit=None
        bookid_form_data = self.get_form_data(self.form_bookid)
        if bookid_form_data:
            bookid = bookid_form_data['setbookid']
            self.will_edit=bookid
            keys = self.form_admin_book.db_columns()
            q = conn.select_dict('books', keys, { 'bookid': bookid } ).fetchone()
            assert q is not None
            defaults = dict(zip(keys, q))
            self.form_admin_book.set_defaults( defaults )
            if self.form_isbn:
                self.form_isbn.append('setbookid', label='', type='hidden', value=bookid)

        if self.form_isbn:
            if self.act_isbn(): return
        else:
            self.info("ISBN 录入方式未启用")

        form_data = self.get_form_data(self.form_admin_book)
        if not form_data: return
        bookid = form_data['bookid']
        keys = self.form_admin_book.db_columns()
        q=conn.select_dict('books', keys, { 'bookid': bookid } ).fetchone()
        assert q is not None
        
        current_data = dict(zip( keys, q ) )
        updated = form_data
        for key in keys:
            if current_data[key] is not None and str(current_data[key]) == str(updated.get(key)):
                del updated[key]
        if updated:
            keys = updated.keys()
            vals = [ updated[k] for k in keys ]
            assert 'bookid' not in keys
            sql = 'UPDATE books SET ' + ('=(?), '.join(keys)) + '=(?) WHERE bookid=(?)' 
            args = vals + [bookid,]
            conn.execute( sql, args )
            self.succeed("已修改: " + '; '.join(keys))

    def html_main(self):
        if self.will_edit:
            form = self.form_admin_book
            div=Div(form)
            if self.form_isbn: div.append(self.form_isbn)
            return div
        else:
            return self.form_bookid
# }}}
# module: admin_delete_book {{{
class mod_admin_delete_book( mod_benyue, mod_virtual_view_books ):
    text='删除书目'
    title='删除书目'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "bookid", required=1, check_db=1)
        self.form1=form
    def act_main(self):
        self.will_delete=None
        form_data = self.get_form_data(self.form1)
        if form_data:
            self.will_delete=form_data['bookid']
        if session.get_value('confirmed') == "确定删除":
            bookid = session.get_value('bookid')
            try:
                with conn:
                    conn.execute('DELETE FROM bookInstances WHERE bookid=(?)', (bookid,))
                    conn.execute('DELETE FROM books WHERE bookid=(?)', (bookid,))
            except sqlite3.IntegrityError:
                return self.fail("不能删除")
            else:
                return self.succeed("已删除: "+bookid)
    def html_main(self):
        if not self.will_delete:
            return self.form1
        div=Div()
        bookid=self.will_delete
        div.append(self.html_book_info(bookid))
        div.append(BR())
        div.append(self.html_book_copies_info_list(bookid))
        div.append(BR())

        form=Form(cgi=self.url, submit='')
        form.append( Input(type='submit', name='confirmed', value="确定删除") )
        form.append( Input(type='submit', name='confirmed', value="取消") )
        form.append( Input(type='hidden', name='bookid', value=bookid) )
        div.append(form)

        return div
# }}}
# module: admin_add_copy {{{
class mod_admin_add_copy( mod_admin_book ):
    text='添加复本'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "bookid", check_db=1, required=1)
        form.append( "quantity", required=1, value=1, check=lambda x: int(x) > 0 )
        form.append( "serial" )
        form.append( "override_circulatable", required=1 )
        form.perform_continue()
        self.form1 = form

    def act_main(self):
        bookid = session.get_value('setbookid')
        if bookid:
            self.form1.set_defaults( {'bookid': bookid } )
            self.form1.set_focus('quantity')
        form_data = self.get_form_data(self.form1)
        if not form_data: return

        bookid = form_data['bookid']
        quantity = form_data['quantity']
        circulatable = form_data['override_circulatable']
        if not conn.select_dict('books', '*', { 'bookid': bookid } ).fetchone():
            return self.fail("索书号错误")
        if 'serial' in form_data.keys():
            serial = form_data['serial']
            if not conn.select_dict('bookInstances', '*', { 'serial': serial } ).fetchone():
                return self.fail("书号已存在")
            if quantity != 1:
                return self.fail("一次一本")
        else:
            serial = None

        self.add_copy( bookid, quantity, serial, circulatable)

    def html_main(self):
        return self.form1
# }}}
# module: admin_rm_copy {{{
class mod_admin_rm_copy( mod_admin_book ):
    text='删除复本'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "bookid", check_db=1, required=1)
        form.append( "serial", check_db=1, required=1)
        self.form1 = form

    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return

        bookid = form_data['bookid']
        serial = form_data['serial']
        if not conn.select_dict('bookInstances', '*', form_data ).fetchone():
            return self.fail("没有这本书")
        if conn.select_dict('borrows', '*', {'serial': serial} ).fetchone():
            return self.fail("已借出，不能删除")
        conn.execute("DELETE FROM bookInstances WHERE serial=(?);", (serial,) )
        self.succeed('已删除')

    def html_main(self):
        return self.form1
# }}}

# module: admin_define_category {{{
class mod_admin_define_category( mod_benyue ):
    text='定义分类'
    def prepare_forms(self):
        form = myForm(self.url,)
        _sub_cats = [ (chr(x), chr(x)) for x in range( ord('A'), ord('Z')+1 ) ]
        form.append( "book_parent_category" , label="父类别" , required=1, )
        form.append( "book_sub_category" , label="子类别" , required=1, )
        form.append( "_category_desc" , label="描述" , required=1 )
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        sub = form_data['book_sub_category']
        parent = form_data['book_parent_category']
        desc = form_data['_category_desc']
        text = parent + sub
        q = conn.select('bookCategories', 'book_category_desc', 'book_category_text', text).fetchone()
        if q: return self.fail('类别已存在:  ' + text + ' ' + q[0])
        conn.insert('bookCategories', ('book_category_text', 'book_category_desc', 'book_category_parent'), ( text, desc, parent ) )
        self.succeed('添加成功')
    def html_main(self):
        return self.form1
# }}}

# base module: admin_user {{{
class mod_admin_user( mod_benyue, mod_virtual_finement ):
    def admin_user_prepare_form(self):
        q=conn.execute('SELECT type, user_type_name FROM userTypes;').fetchall()
        user_types = []
        for t, n in q:
            user_types.append( (n,t) )

        form = myForm(self.url)
        form.append( "uid", required=1)
        form.append( "name", required=1)
        form.append( "pwd", required=1)
        form.append( "contact")
        form.append( "type", required=1)
        form.append( "revoke_date", required=1)
        form.perform_continue()
        self.form_admin_user=form
# }}}
# module: admin_add_user {{{
class mod_admin_add_user( mod_admin_user ):
    text='添加用户'
    def prepare_forms(self):
        self.admin_user_prepare_form()
        self.form_admin_user.value="添加"
        revoke_on = datetime.date.today() + datetime.timedelta(366,0,0)
        revoke_on = revoke_on.strftime('%Y-%m-%d')
        self.form_admin_user.set_defaults( { 'revoke_date': revoke_on } )
    def act_main(self):
        form_data = self.get_form_data(self.form_admin_user)
        if not form_data: return
        if form_data.get('type') == 0 and not re.match('^0[0-9]{7}$|1[0-9]{9}$', form_data.get('uid') or ''): 
            return self.fail("用户名错误")
        if conn.select_dict('allusers', '*', { 'uid': form_data['uid'] } ).fetchone():
            return self.fail("该用户已存在")
        conn.insert_dict('allusers', form_data)
        self.succeed("已添加")

    def html_main(self):
        return self.form_admin_user
# }}}
# module: admin_batch_add_user {{{
class mod_admin_batch_add_user( mod_admin_user ):
    text='批量添加用户'
    def prepare_forms(self):
        form = myForm(self.url, name='batch_add_user', value="批量添加")
        form.append( 'type', required=1, fixed=1, value=0, label_value="普通用户")
        form.append( 'revoke_date', required=1)
        form.append( 'textarea', label="学号,姓名", rows=20, _format_check_ = lambda x: re.match( r"^\n*([0-9]+\s*,\s*[^,]*\n)*([0-9]+\s*,\s*[^,]*)\n*$", x ), required=1 )
        revoke_on = datetime.date.today() + datetime.timedelta(366*4-30*2,0,0)
        revoke_on = revoke_on.strftime('%Y-%m-%d')
        form.set_defaults( { 'revoke_date': revoke_on } )
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        lines = form_data['textarea'].replace("\r","").strip().split("\n")
        revoke_date = form_data['revoke_date']
        try:
            with conn:
                for line in lines:
                    uid, name = re.split('\s*,\s*', line)
                    conn.insert_dict( "allusers", { 'uid': uid, 'name': name, 'revoke_date': revoke_date } )
        except Exception, e:
            return self.fail( "添加失败:" + line + ", " + str(e) )
        else:
            return self.succeed("添加 " + str(len(lines)) + " 用户成功")

    def html_main(self):
        return self.form1
# }}}
# module: admin_edit_user {{{
class mod_admin_edit_user( mod_admin_user ):
    text='修改用户'
    def prepare_forms(self):
        self.admin_user_prepare_form()
        form = self.form_admin_user
        form.append( 'active') 
        form.get('uid').fix()
        form.get('pwd').required=False
        
    def uid_form(self):
        form=Form( cgi = self.url, name="form_uid", submit='' )
        tr=TR( TD("用户名:" ), TD( Input(name='setuid') ), TD( Input( type='submit', value='确定'), width=30 ) )
        tab=TableLite( tr, cellspacing=4)
        form.append( tab )
        return form

    def act_main(self):
        if session.get_value('setuid'):
            uid = session.get_value('setuid')
            if not conn.select_dict('allusers', '*', { 'uid': uid } ).fetchone():
                return self.fail("没有这个用户")

        form_data = self.get_form_data(self.form_admin_user)
        if not form_data: return
        uid = form_data['uid']
        keys = self.form_admin_user.db_columns()
        q=conn.select_dict('allusers', keys, { 'uid': uid } ).fetchone()
        if not q: return self.fail("没有这个用户")
        
        current_data = dict(zip( keys, q ) )
        updated = dict.fromkeys( keys )
        if 'pwd' in updated.keys(): del updated['pwd']
        updated.update(form_data)
        for key in keys:
            if current_data[key] == updated.get(key):
                del updated[key]
        if updated:
            keys = updated.keys()
            vals = [ updated[k] for k in keys ]
            if 'uid' in keys: return self.fail("不能修改用户名")
            if 'type' in keys: return self.fail("不能修改用户类型")
            sql = 'UPDATE allusers SET ' + ('=(?), '.join(keys)) + '=(?) WHERE uid=(?)' 
            args = vals + [uid,]
            conn.execute( sql, args )
            self.succeed("已修改")

    def html_main(self):
        if session.get_value('setuid'):
            form = self.form_admin_user
            uid = session.get_value('setuid')
            keys = form.db_columns()
            if 'pwd' in keys: keys.remove('pwd')
            current_data=conn.select_dict('allusers', keys, { 'uid': uid } ).fetchone()
            if current_data:
                defaults = dict(zip(keys, current_data))
                form.set_defaults( defaults )
                return form
        return self.uid_form()
# }}}
# module: admin_rm_user {{{
class mod_admin_rm_user( mod_benyue ):
    text='删除用户'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "user_id", required=1)
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
        uid = form_data['uid']
        if uid == session.uid:
            return self.fail("不能删除自己")
        if not conn.select('allusers', '*', 'uid', uid).fetchone():
            return self.fail("没有这个用户")
        if conn.select('borrows', '*', 'uid', uid).fetchone():
            return self.fail("该用户有未还图书，不能删除")
        if conn.select('admins', '*', 'uid', uid).fetchone():
            self.fail("不能删除管理员")
            conn.execute('UPDATE allusers SET active = 1 WHERE uid=(?);', (uid,) )
            return self.succeed("已禁用")
        try:
            conn.execute('DELETE FROM allusers WHERE uid=(?);', (uid,) )
        except sqlite3.IntegrityError:
            self.fail("不能删除")
            conn.execute('UPDATE allusers SET active = 1 WHERE uid=(?);', (uid,) )
            self.succeed("已禁用")
        else:
            self.succeed("已删除")
    def html_main(self):
        return self.form1
# }}}
# module: admin_view_users {{{
class mod_admin_view_users( mod_admin_user ):
    text='查看用户'
    def prepare_forms(self):
        form=myForm(self.url, name='view_user')
        form.append('uid')
        form.append('type')
        form.append('_filter_active', label='', db_column=False, type='checkbox', rlabel='所有用户')
        self.form1 = form

    def html_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return self.form1
        if 'uid' in form_data.keys():
            form_data['uid'] = form_data['uid'] + '%'
        if form_data.get('_filter_active'):
            del form_data['_filter_active']
            user_table = 'allusers'
        else:
            sql = 'create temp view tmp_active_users AS select * from allusers WHERE active=0 and revoke_date >= "' + datetime.date.today().strftime('%Y-%m-%d') + '"'
            conn.execute(sql)
            user_table = 'tmp_active_users'
        table =  user_table + ' JOIN userTypes USING(type) JOIN userStatus USING(active)'
        fields = 'uid, name, contact, userTypes.user_type_name, userStatus.user_status_name, create_date, revoke_date'
        data = conn.select_dict( table, fields, form_data, like=True )
        body = map(list,data)
        heading=['用户名', '姓名', '联系方式', '用户类型', '状态', '创建日期', '失效日期', ]
        if form_data['type'] == '0':
            for row in body:
                row.append( self.calc_finement( row[0] ) )
            heading.append('欠款')
        viewer = myViewer()
        viewer.append( heading="用户名", handler = lambda uid: Href( text=uid, url=get_mod('admin_edit_user').url + '&continue=admin_view_users&setuid=' + uid )  )
        for item in heading[1:]:
            viewer.append( heading=item, align='center' )
        return viewer.table( body, width='100%' )
# }}}

# module: admin_clear_finement {{{
class mod_admin_clear_finement( mod_benyue, mod_virtual_finement ):
    text='清除欠款'
    def prepare_forms(self):
        form = myForm(self.url, name='setuid')
        form.append( "uid", required=1)
        self.form_setuid=form

    def act_main(self):
        self.will_clear=None
        form_data = self.get_form_data(self.form_setuid)
        if form_data:
            uid = form_data['uid']
            finement = self.calc_finement(uid)
            q = conn.select_dict('finement', 'finement', {'uid': uid} ).fetchone()
            finement_base = q and q[0] or 0
            if finement == 0:
                return self.succeed("没有欠款")
            elif finement_base == 0:
                return self.succeed("请先还书")
            else:
                self.will_clear=uid
        if session.get_value('clear') == "确定":
            uid = session.get_value('uid')
            conn.execute('DELETE FROM finement WHERE uid=(?)', (uid,))
            return self.succeed("已清除")

    def html_main(self):
        if not self.will_clear:
            return self.form_setuid
        form_data = self.get_form_data(self.form_setuid)

        uid = self.will_clear
        name, finement_base = conn.select_dict('users LEFT JOIN finement using(uid)', ['name','finement'], {'uid': uid} ).fetchone()
        finement_all = self.calc_finement(uid)
        remained = finement_all - finement_base

        form=Form(cgi=self.url, submit='')
        form.append( Input(type='submit', name='clear', value="确定") )
        form.append( Input(type='submit', name='clear', value="取消") )
        form.append( Input(type='hidden', name='uid', value=uid) )

        div=Div(style='font-size: 16')
        info = uid + " " + name + ":  可清除欠款" + str(finement_base) + '元, 剩余欠款' + str(remained) + "元"
        div.append( info, BR(), BR(), form )
        return div

# }}}

# module: admin_view_borrows {{{
class mod_admin_view_borrows( mod_benyue ):
    text='查看借书'
    def html_main(self):
        sql = '''SELECT books.bookid, books.title, users.uid, users.name, borrows.serial, borrows.borrow_datetime, borrows.due_datetime FROM 
                    borrows JOIN bookInstances USING (serial)
                    JOIN books USING (bookid)
                    JOIN users USING (uid);
                    '''
        output = conn.execute(sql).fetchall()
        body = map(list,output)
        heading=['索书号', '书名', '用户名', '姓名', '书号', '借出时间', '应还时间']
        tab = Table( border=0, heading=heading, column1_align='center', cell_align='center', body=body )
        return tab
# }}}

# module: about {{{
class mod_about( mod_benyue ):
    text='关于'
    def _check_enabled(self):
        path='about.html'
        if os.access(path, os.R_OK):
            return True
        else:
            return False
    def html(self):
        path='about.html'
        page=open(path, 'r').read()
        return RawText(page)
# }}}
# module: author {{{
class mod_author( mod_benyue ):
    text='联系作者'
    def html(self):
        path='author.html'
        try:
            page=open(path, 'r').read()
            return RawText(page)
        except:
            return Text(my_contact)
# }}}

# module: example {{{
class mod_example( mod_benyue ):
    text='__EXAMPLE__'
    def prepare_forms(self):
        form = myForm(self.url)
        form.append( "name" )
        self.form1=form
    def act_main(self):
        form_data = self.get_form_data(self.form1)
        if not form_data: return
    def html_main(self):
        return self.form1
# }}}
# }}}

# mySession {{{
import pickle
class mySession():
    my_session_timeout=15
    def __init__(self):
        self.session_info=[]
        self.debuginfo=''
        self.set_cookie=''
        self.session_data={}
        self.set_user()
        try:
            self.ip=os.environ['REMOTE_ADDR']
        except KeyError:
            pass
        if not self.load_from_cookie():
            self.login(None)
        self.input_form = cgi.FieldStorage()
        self.current_act = self.get_value('act')

    def set_user(self, uid='', sid='', user_type=-1):
        self.uid=uid
        self.sid=sid
        self.user_type=int(user_type)
        with conn:
            conn.execute("DELETE FROM adminCurrent")
            if self.user_type > 0:
                conn.execute("INSERT INTO adminCurrent (uid) VALUES (?)", (self.uid,) )
        self.allowed_modules = user_allowed_modules[self.user_type]
        self.current_act = self.allowed_modules[0]

    def get_value(self, field):
        return self.input_form.getvalue(field)

    def get_values_dict(self, fields, ignore_empty=True):
        rt={}
        for field in fields:
            val = self.input_form.getvalue(field)
            if not val and ignore_empty: continue
            rt[field] = val
        return rt

    def get_act(self):
        c = self.current_act 
        if not c: c = self.allowed_modules[0]
        if c not in self.allowed_modules:
            c='unauthorized'
        if not has_mod(c):
            c='unavailable'
        return c

    def load_from_cookie(self):
        string_cookie = os.environ.get('HTTP_COOKIE')
        self.string_cookie=string_cookie
        if not string_cookie: return False
        try:
            cookie=Cookie.SimpleCookie()
            cookie.load(string_cookie)
            sid = cookie['benyue_sid'].value
        except:
             sid = ''
        if not sid: return False
        q = conn.execute("SELECT uid, type, last_active, data FROM sessions WHERE sid = (?) and ip=(?)", ( sid, self.ip ) ).fetchone()
        if not q: return False
        uid, tid, last_active, session_data_dump = q
        if tid is None: tid = -1

        now = datetime.datetime.now() 
        datetime_limit = now - datetime.timedelta(minutes=self.my_session_timeout)
        if last_active < datetime_limit.strftime('%Y-%m-%d %H:%M:%S'):
            conn.execute('DELETE FROM sessions WHERE sid = (?)', (sid,) )
            return False
        else:
            conn.execute("UPDATE sessions SET last_active = (?) WHERE sid = (?)", ( now.strftime('%Y-%m-%d %H:%M:%S'), sid ) )
            self.set_user(uid, sid, tid)
            if session_data_dump:
                self.session_data=pickle.loads(session_data_dump)
            return True

    def get_session_data(self, key):
        if key in self.session_data.keys():
            return self.session_data[key]

    def set_session_data(self, key, val):
        self.session_data[key] = val
        data = pickle.dumps( self.session_data )
        conn.execute("UPDATE sessions SET data = (?) WHERE sid = (?)", ( data, self.sid ) )

    def output_header(self):
        print "Content-type: text/html"
        if self.set_cookie: print self.set_cookie
        print

    def create_sid(self):
        import random
        sid = hashlib.md5(repr(random.randint(100,999))).hexdigest()
        if conn.execute('SELECT * FROM sessions WHERE sid=(?)', (sid,) ).fetchone():
            return self.create_sid()
        else:
            return sid

    def logout(self):
        self.login(None)

    def login(self, uid=None, user_type=-1):
        if self.sid:
            conn.execute('DELETE FROM sessions WHERE sid=(?)', (self.sid,) )
        self.set_user( uid, self.create_sid(), user_type )
        with conn:
            if uid:
                conn.execute('DELETE FROM sessions WHERE uid=(?)', (self.uid,) )
                conn.execute('INSERT INTO sessions (uid, sid, type, ip) VALUES (?,?,?,?)', (self.uid, self.sid, self.user_type, self.ip) )
            else:
                conn.execute('INSERT INTO sessions ( sid, ip ) VALUES (?,?)', ( self.sid, self.ip) )
        self.create_cookie()
        
    def create_cookie(self):
        cookie=Cookie.SimpleCookie()
        cookie['benyue_sid'] = self.sid
        self.set_cookie = cookie.output()

def has_mod(name, virtual=False):
    if not virtual:
        return name in benyue_modules
    else:
        return name in benyue_virtual_modules

def has_virtual_mod(name):
    return has_mod(name, virtual=True)

def get_mod(name=None, virtual=False):
    if name == None: name=session.get_act()
    if not virtual:
        return benyue_modules.get(name)
    else:
        return benyue_virtual_modules.get(name)

def get_virtual_mod(name):
    return get_mod(name, virtual=True)

def init_mods():
    g=globals()
    for name in g:
        cls=g[name]
        try:
            assert issubclass(cls, mod_benyue_base)
        except AssertionError:
            continue
        except TypeError:
            continue
        inst = cls()
        if inst.disabled: continue
        if inst.virtual:
            short_name = name.replace('mod_virtual_','')
            benyue_virtual_modules[short_name] = inst
        else:
            short_name = name.replace('mod_','')
            benyue_modules[short_name] = inst
# }}}

# generate html {{{
def create_left_panel():
    lptab=TableLite( border=0 )
    for item in session.allowed_modules:
        mod = get_mod(item)
        if not mod: continue
        if not mod.text: continue
        link = Href( text = mod.text, url = mod.url )
        lptab.append( link, P() )

    lp=Div(lptab, style="color:black;")
    return lp

def create_page_body():
    return get_mod().html()

def create_page_bottom():
    bt=Div(my_bottom_line ,style='font-size: 10pt', align='center') 
    return bt

def create_page_top():
    tab=TableLite( border=0 , width="100%")
    tr=TR(style='height: 40;')
    tr.append(
        TD( Span(Text(my_heading), style='font-size: 14.0pt;'), bgcolor="lightblue", align='center', valign='center'),
        TD( Text(session.uid) , width=100),
    )
    tab.append(tr)
    return tab

def create_document():
    doc=SimpleDocument()
    doc.title=my_title
    doc.meta = Meta(equiv="Content-Type",content="text/html;charset=utf-8")

    if my_heading: doc.append(create_page_top())

    tab=TableLite( border=0 , width="100%", style='height: 100%;')
    lp = create_left_panel()
    page_body = create_page_body()
    tab.append( TR( 
        TD( lp , bgcolor="lightblue", width=80, valign="top") ,
        TD( width="20" ) ,
        TD( page_body , valign="top") 
    ) )
    tab.append( TR( TD( create_page_bottom(), colspan="3" ), style='height: 20' ) )
    doc.append(tab)
    return doc

# }}}


import sys
# run_manage {{{
def run_help():
    print my_version
    print
    print '''config file is my_benyue_settings.py.'''

def run_manage():
    args = sys.argv[1:]
    if not len(args):
        run_help()
        return
    if args[0] == "init":
        init_database()
# }}}
# run_cgi {{{
def run_cgi():
    if my_under_maintenence:
        print "Content-type: text/html"
        print 
        print "System Under Maintenence."
        sys.exit(0)

    create_database_connection()
    init_mods()
    global session
    session=mySession()
    get_mod().act()
    session.output_header()
    doc = create_document()
    print doc
    print Text(session.debuginfo)
    conn.close()
# }}}

def main():
    if 'SCRIPT_FILENAME' in os.environ.keys():
        run_cgi()
    else:
        run_manage()

if __name__ == '__main__':
    main()

